using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Threading;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.transaction
{


	using HeuristicMixedException = javax.transaction.HeuristicMixedException;
	using HeuristicRollbackException = javax.transaction.HeuristicRollbackException;
	using NotSupportedException = javax.transaction.NotSupportedException;
	using RollbackException = javax.transaction.RollbackException;
	using Status = javax.transaction.Status;
	using SystemException = javax.transaction.SystemException;
	using Transaction = javax.transaction.Transaction;
	using TransactionManager = javax.transaction.TransactionManager;
	using XAException = javax.transaction.xa.XAException;
	using XAResource = javax.transaction.xa.XAResource;
	using Xid = javax.transaction.xa.Xid;

	using StoreFailureException = org.neo4j.kernel.impl.nioneo.store.StoreFailureException;
	using XaResource = org.neo4j.kernel.impl.transaction.xaframework.XaResource;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

///
/// <summary> * Public for testing purpose only. Use <seealso cref="TransactionFactory"/> to get a
/// * <CODE>UserTransaction</CODE>, don't use this class.
/// * <p> </summary>
/// 
	public class TxManager : TransactionManager
	{
		private static Logger log = Logger.getLogger(typeof(TxManager).Name);

		private ArrayMap<Thread, TransactionImpl> txThreadMap;

		private readonly string txLogDir;
		private string separator = "/";
		private string logSwitcherFileName = "active_tx_log";
		private string txLog1FileName = "tm_tx_log.1";
		private string txLog2FileName = "tm_tx_log.2";
		private int maxTxLogRecordCount = 1000;
		private int eventIdentifierCounter = 0;

		private TxLog txLog = null;
		private XaDataSourceManager xaDsManager = null;
		private bool tmOk = false;

		internal TxManager(string txLogDir)
		{
			this.txLogDir = txLogDir;
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual int getNextEventIdentifier()
		{
			return eventIdentifierCounter++;
		}

		internal virtual void stop()
		{
			if (txLog != null)
			{
				try
				{
					txLog.Close();
				}
				catch (IOException e)
				{
					log.warning("Unable to close tx log[" + txLog.Name + "]" + ", " + e);
				}
			}
		}

		internal virtual void init(XaDataSourceManager xaDsManagerToUse)
		{
			this.xaDsManager = xaDsManagerToUse;
			txThreadMap = new ArrayMap<Thread, TransactionImpl>(5, true, true);
			separator = System.getProperty("file.separator");
			logSwitcherFileName = txLogDir + separator + "active_tx_log";
			txLog1FileName = "tm_tx_log.1";
			txLog2FileName = "tm_tx_log.2";
			try
			{
				if (new File(logSwitcherFileName).exists())
				{
					FileChannel fc = new RandomAccessFile(logSwitcherFileName, "rw").getChannel();
					sbyte[] fileName = new sbyte[256];
					ByteBuffer buf = ByteBuffer.wrap(fileName);
					fc.read(buf);
					fc.Close();
					string currentTxLog = txLogDir + separator + new string(fileName).Trim();
					if (!new File(currentTxLog).exists())
					{
						throw new TransactionFailureException("Unable to start TM, " + "active tx log file[" + currentTxLog + "] not found.");
					}
					txLog = new TxLog(currentTxLog);
				}
				else
				{
					if (new File(txLogDir + separator + txLog1FileName).exists() || new File(txLogDir + separator + txLog2FileName).exists())
					{
						throw new TransactionFailureException("Unable to start TM, " + "no active tx log file found but found either " + txLog1FileName + " or " + txLog2FileName + " file, please set one of them as active or " + "remove them.");
					}
					ByteBuffer buf = ByteBuffer.wrap(txLog1FileName.getBytes("UTF-8"));
					FileChannel fc = new RandomAccessFile(logSwitcherFileName, "rw").getChannel();
					fc.write(buf);
					txLog = new TxLog(txLogDir + separator + txLog1FileName);
					fc.force(true);
					fc.Close();
				}
				IEnumerator <List<TxLog.Record>> danglingRecordList = txLog.getDanglingRecords();
				if (danglingRecordList.MoveNext())
				{
					log.info("Unresolved transactions found, " + "recovery started ...");
					recover(danglingRecordList);
					log.info("Recovery completed, all transactions have been " + "resolved to a consistent state.");
				}
				getTxLog().truncate();
				tmOk = true;
			}
			catch (IOException e)
			{
				log.severe("Unable to start TM");
				throw new TransactionFailureException("Unable to start TM", e);
			}
		}

		lock TxLog getTxLog()
			throws IOException;
		{
			if (txLog.getRecordCount() > maxTxLogRecordCount)
			{
				if (txLog.Name.EndsWith(txLog1FileName))
				{
					txLog.switchToLogFile(txLogDir + separator + txLog2FileName);
					changeActiveLog(txLog2FileName);
				}
				else if (txLog.Name.EndsWith(txLog2FileName))
				{
					txLog.switchToLogFile(txLogDir + separator + txLog1FileName);
					changeActiveLog(txLog1FileName);
				}
				else
				{
					tmOk = false;
					log.severe("Unknown active tx log file[" + txLog.Name + "], unable to switch.");
					throw new IOException("Unknown txLogFile[" + txLog.Name + "] not equals to either [" + txLog1FileName + "] or [" + txLog2FileName + "]");
				}
			}
			return txLog;
		}

		private void changeActiveLog(string newFileName) throws IOException
		{
		// change active log
			FileChannel fc = new RandomAccessFile(logSwitcherFileName, "rw").getChannel();
			ByteBuffer buf = ByteBuffer.wrap(newFileName.getBytes());
			fc.truncate(0);
			fc.write(buf);
			fc.force(true);
			fc.Close();
		}

		void setTmNotOk()
		{
			tmOk = false;
		}

		private void recover(IEnumerator <List<TxLog.Record>> danglingRecordList)
		{
			try
			{
			// contains NonCompletedTransaction that needs to be committed
				List<NonCompletedTransaction> commitList = new List<NonCompletedTransaction>();

			// contains Xids that should be rolledback
				List<Xid> rollbackList = new LinkedList<Xid>();

			// key = Resource(branchId) value = XAResource
				Map<Resource, XAResource> resourceMap = new Dictionary<Resource, XAResource>();
				buildRecoveryInfo(commitList, rollbackList, resourceMap, danglingRecordList);
			// invoke recover on all xa resources found
				IEnumerator <Resource> resourceItr = resourceMap.Keys.GetEnumerator();
				List<Xid> recoveredXidsList = new LinkedList<Xid>();
				while (resourceItr.MoveNext())
				{
					XAResource xaRes = resourceMap.get(resourceItr.Current);
					Xid[] xids = xaRes.recover(XAResource.TMNOFLAGS);
					for (int i = 0; i < xids.Length; i++)
					{
						if (XidImpl.isThisTm(xids[i].getGlobalTransactionId()))
						{
						// linear search
							if (rollbackList.Contains(xids[i]))
							{
								log.fine("Found pre commit " + xids[i] + " rolling back ... ");
								rollbackList.Remove(xids[i]);
								xaRes.rollback(xids[i]);
							}
							else
							{
								recoveredXidsList.Add(xids[i]);
							}
						}
						else
						{
							log.warning("Unknown xid: " + xids[i]);
						}
					}
				}
			// sort the commit list after sequence number
				Collections.sort(commitList, new Comparator<NonCompletedTransaction>() { public int compare(NonCompletedTransaction r1, NonCompletedTransaction r2) { return r1.getSequenceNumber() - r2.getSequenceNumber(); } });
			// go through and commit
				List<NonCompletedTransaction>.Enumerator commitItr = commitList.GetEnumerator();
				while (commitItr.MoveNext())
				{
					NonCompletedTransaction nct = commitItr.Current;
					int seq = nct.getSequenceNumber();
					Xid[] xids = nct.getXids();
					log.fine("Marked as commit tx-seq[" + seq + "] branch length: " + xids.Length);
					for (int i = 0; i < xids.Length; i++)
					{
						if (!recoveredXidsList.Contains(xids[i]))
						{
							log.fine("Tx-seq[" + seq + "][" + xids[i] + "] not found in recovered xid list, " + "assuming already committed");
							continue;
						}
						recoveredXidsList.Remove(xids[i]);
						Resource resource = new Resource(xids[i].getBranchQualifier());
						if (!resourceMap.containsKey(resource))
						{
							throw new TransactionFailureException("Couldn't find XAResource for " + xids[i]);
						}
						log.fine("Commiting tx seq[" + seq + "][" + xids[i] + "] ... ");
						resourceMap.get(resource).commit(xids[i], false);
					}
				}
			// rollback the rest
				List<Xid>.Enumerator rollbackItr = recoveredXidsList.GetEnumerator();
				while (rollbackItr.MoveNext())
				{
					Xid xid = rollbackItr.Current;
					Resource resource = new Resource(xid.getBranchQualifier());
					if (!resourceMap.containsKey(resource))
					{
						throw new TransactionFailureException("Couldn't find XAResource for " + xid);
					}
					log.fine("Rollback " + xid + " ... ");
					resourceMap.get(resource).rollback(xid);
				}
				if (rollbackList.Count > 0)
				{
					log.fine("TxLog contained unresolved " + "xids that needed rollback. They couldn't be matched to " + "any of the XAResources recover list. " + "Assuming " + rollbackList.Count + " transactions already rolled back.");
				}
			}
			catch (XAException e)
			{
				throw new TransactionFailureException("Recovery failed." + e);
			}
		}

		private void buildRecoveryInfo(List<NonCompletedTransaction> commitList, List<Xid> rollbackList, Map<Resource, XAResource> resourceMap, IEnumerator <List<TxLog.Record>> danglingRecordList)
		{
			while (danglingRecordList.MoveNext())
			{
				IEnumerator <TxLog.Record> dListItr = danglingRecordList.Current.GetEnumerator();
				TxLog.Record startRecord = dListItr.Current;
				if (startRecord.getType() != TxLog.TX_START)
				{
					throw new TransactionFailureException("First record not a start record, type=" + startRecord.getType());
				}
			// get branches & commit status
				HashSet<Resource> branchSet = new HashSet<Resource>();
				int markedCommit = -1;
				while (dListItr.MoveNext())
				{
					TxLog.Record record = dListItr.Current;
					if (record.getType() == TxLog.BRANCH_ADD)
					{
						if (markedCommit != -1)
						{
							throw new TransactionFailureException("Already marked commit " + startRecord);
						}
						branchSet.Add(new Resource(record.getBranchId()));
					}
					else if (record.getType() == TxLog.MARK_COMMIT)
					{
						if (markedCommit != -1)
						{
							throw new TransactionFailureException("Already marked commit " + startRecord);
						}
						markedCommit = record.getSequenceNumber();
					}
					else
					{
						throw new TransactionFailureException("Illegal record type[" + record.getType() + "]");
					}
				}
				HashSet<Resource>.Enumerator resourceItr = branchSet.GetEnumerator();
				List<Xid> xids = new LinkedList<Xid>();
				while (resourceItr.MoveNext())
				{
					Resource resource = resourceItr.Current;
					if (!resourceMap.containsKey(resource))
					{
						resourceMap.put(resource, getXaResource(resource.getResourceId()));
					}
					xids.Add(new XidImpl(startRecord.getGlobalId(), resource.getResourceId()));
				}
				if (markedCommit != -1) // this xid needs to be committed
				{
					commitList.Add(new NonCompletedTransaction(markedCommit, xids));
				}
				else
				{
					rollbackList.addAll(xids);
				}
			}
		}

		private static class NonCompletedTransaction
		{
			private int seqNr = -1;
			private List<Xid> xidList = null;

			NonCompletedTransaction(int seqNr, List<Xid> xidList)
			{
				this.seqNr = seqNr;
				this.xidList = xidList;
			}

			int getSequenceNumber()
			{
				return seqNr;
			}

			Xid[] getXids()
			{
				return xidList.ToArray(new XidImpl[xidList.size()]);
			}
		}

		private static class Resource
		{
			private sbyte resourceId[] = null;

			Resource(sbyte resourceId[])
			{
				if (resourceId == null || resourceId.length == 0)
				{
					throw new IllegalArgumentException("Illegal resourceId");
				}
				this.resourceId = resourceId;
			}

			sbyte[] getResourceId()
			{
				return resourceId;
			}

			public bool Equals(object o)
			{
				if (!(o is Resource))
				{
					return false;
				}
				sbyte[] otherResourceId = ((Resource) o).getResourceId();

				if (resourceId.length != otherResourceId.Length)
				{
					return false;
				}
				for (int i = 0; i < resourceId.length; i++)
				{
					if (resourceId[i] != otherResourceId[i])
					{
						return false;
					}
				}
				return true;
			}

			private volatile int hashCode = 0;

			public int GetHashCode()
			{
				if (hashCode == 0)
				{
					int calcHash = 0;
					for (int i = 0; i < resourceId.length; i++)
					{
						calcHash += resourceId[i] << i * 8;
					}
					hashCode = 3217 * calcHash;
				}
				return hashCode;
			}
		}

		public void begin() throws NotSupportedException, SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}

			Thread thread = Thread.CurrentThread;
			TransactionImpl tx = txThreadMap.get(thread);
			if (tx != null)
			{
				throw new NotSupportedException("Nested transactions not supported");
			}
			tx = new TransactionImpl(this);
			txThreadMap.put(thread, tx);
		// start record written on resource enlistment
		}

	// called when a resource gets enlisted
		void writeStartRecord(sbyte globalId[]) throws SystemException
		{
			try
			{
				getTxLog().txStart(globalId);
			}
			catch (IOException e)
			{
				e.printStackTrace();
				log.severe("Error writing transaction log");
				tmOk = false;
				throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
			}
		}

		public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, IllegalStateException, SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
			Thread thread = Thread.CurrentThread;
			TransactionImpl tx = txThreadMap.get(thread);
			if (tx == null)
			{
				throw new IllegalStateException("Not in transaction");
			}
			if (tx.getStatus() != Status.STATUS_ACTIVE && tx.getStatus() != Status.STATUS_MARKED_ROLLBACK)
			{
				throw new IllegalStateException("Tx status is: " + getTxStatusAsString(tx.getStatus()));
			}
			tx.doBeforeCompletion();
		// delist resources?
			if (tx.getStatus() == Status.STATUS_ACTIVE)
			{
				commit(thread, tx);
			}
			else if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
			{
				rollbackCommit(thread, tx);
			}
			else
			{
				throw new IllegalStateException("Tx status is: " + getTxStatusAsString(tx.getStatus()));
			}
		}

		private void commit(Thread thread, TransactionImpl tx) throws SystemException, HeuristicMixedException, HeuristicRollbackException
		{
		// mark as commit in log done TxImpl.doCommit()
			StoreFailureException sfe = null;
			int xaErrorCode = -1;
			if (tx.getResourceCount() == 0)
			{
				tx.setStatus(Status.STATUS_COMMITTED);
			}
			else
			{
				try
				{
					tx.doCommit();
				}
				catch (XAException e)
				{
					xaErrorCode = e.errorCode;
					e.printStackTrace();
					log.severe("Commit failed, status=" + getTxStatusAsString(tx.getStatus()) + ", errorCode=" + xaErrorCode);
					if (tx.getStatus() == Status.STATUS_COMMITTED)
					{
					// this should never be
						tmOk = false;
						throw new TransactionFailureException("commit threw exception but status is committed?", e);
					}
				}
				catch (StoreFailureException e)
				{
					sfe = e;
				}
			}
			if (tx.getStatus() != Status.STATUS_COMMITTED)
			{
				try
				{
					tx.doRollback();
				}
				catch (XAException e)
				{
					e.printStackTrace();
					log.severe("Unable to rollback transaction. " + "Some resources may be commited others not. " + "Neo4j kernel should be SHUTDOWN for " + "resource maintance and transaction recovery ---->");
					tmOk = false;
					if (sfe != null)
					{
						sfe.printStackTrace();
					}
					throw new HeuristicMixedException("Unable to rollback ---> error code in commit: " + xaErrorCode + " ---> error code for rollback: " + e.errorCode);
				}
				tx.doAfterCompletion();
				txThreadMap.Remove(thread);
				try
				{
					if (tx.isGlobalStartRecordWritten())
					{
						getTxLog().txDone(tx.getGlobalId());
					}
				}
				catch (IOException e)
				{
					e.printStackTrace();
					log.severe("Error writing transaction log");
					tmOk = false;
					throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
				}
				tx.setStatus(Status.STATUS_NO_TRANSACTION);
				if (sfe == null)
				{
					throw new HeuristicRollbackException("Failed to commit, transaction rolledback ---> " + "error code was: " + xaErrorCode);
				}
				else
				{
					throw new HeuristicRollbackException("Failed to commit, transaction rolledback ---> " + sfe);
				}
			}
			tx.doAfterCompletion();
			txThreadMap.Remove(thread);
			try
			{
				if (tx.isGlobalStartRecordWritten())
				{
					getTxLog().txDone(tx.getGlobalId());
				}
			}
			catch (IOException e)
			{
				e.printStackTrace();
				log.severe("Error writing transaction log");
				tmOk = false;
				throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
			}
			tx.setStatus(Status.STATUS_NO_TRANSACTION);
		}

		private void rollbackCommit(Thread thread, TransactionImpl tx) throws HeuristicMixedException, RollbackException, SystemException
		{
			try
			{
				tx.doRollback();
			}
			catch (XAException e)
			{
				e.printStackTrace();
				log.severe("Unable to rollback marked transaction. " + "Some resources may be commited others not. " + "Neo4j kernel should be SHUTDOWN for " + "resource maintance and transaction recovery ---->");
				tmOk = false;
				throw new HeuristicMixedException("Unable to rollback " + " ---> error code for rollback: " + e.errorCode);
			}

			tx.doAfterCompletion();
			txThreadMap.Remove(thread);
			try
			{
				if (tx.isGlobalStartRecordWritten())
				{
					getTxLog().txDone(tx.getGlobalId());
				}
			}
			catch (IOException e)
			{
				e.printStackTrace();
				log.severe("Error writing transaction log");
				tmOk = false;
				throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
			}
			tx.setStatus(Status.STATUS_NO_TRANSACTION);
			throw new RollbackException("Failed to commit, transaction rolledback");
		}

		public void rollback() throws IllegalStateException, SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
			Thread thread = Thread.CurrentThread;
			TransactionImpl tx = txThreadMap.get(thread);
			if (tx == null)
			{
				throw new IllegalStateException("Not in transaction");
			}
			if (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_MARKED_ROLLBACK || tx.getStatus() == Status.STATUS_PREPARING)
			{
				tx.doBeforeCompletion();
			// delist resources?
				try
				{
					tx.doRollback();
				}
				catch (XAException e)
				{
					e.printStackTrace();
					log.severe("Unable to rollback marked or active transaction. " + "Some resources may be commited others not. " + "Neo4j kernel should be SHUTDOWN for " + "resource maintance and transaction recovery ---->");
					tmOk = false;
					throw new SystemException("Unable to rollback " + " ---> error code for rollback: " + e.errorCode);
				}
				tx.doAfterCompletion();
				txThreadMap.Remove(thread);
				try
				{
					if (tx.isGlobalStartRecordWritten())
					{
						getTxLog().txDone(tx.getGlobalId());
					}
				}
				catch (IOException e)
				{
					e.printStackTrace();
					log.severe("Error writing transaction log");
					tmOk = false;
					throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
				}
				tx.setStatus(Status.STATUS_NO_TRANSACTION);
			}
			else
			{
				throw new IllegalStateException("Tx status is: " + getTxStatusAsString(tx.getStatus()));
			}
		}

		public int getStatus() // throws SystemException
		{
			Thread thread = Thread.CurrentThread;
			TransactionImpl tx = txThreadMap.get(thread);
			if (tx != null)
			{
				return tx.getStatus();
			}
			return Status.STATUS_NO_TRANSACTION;
		}

		public Transaction getTransaction()
		{
			return txThreadMap.get(Thread.CurrentThread);
		}

		public void resume(Transaction tx) throws IllegalStateException, SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
			Thread thread = Thread.CurrentThread;
			if (txThreadMap.get(thread) != null)
			{
				throw new IllegalStateException("Transaction already associated");
			}
			if (tx != null)
			{
				TransactionImpl txImpl = (TransactionImpl) tx;
				if (txImpl.getStatus() != Status.STATUS_NO_TRANSACTION)
				{
					txImpl.markAsActive();
					txThreadMap.put(thread, txImpl);
				}
			// generate pro-active event resume
			}
		}

		public Transaction suspend() throws SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
		// check for ACTIVE/MARKED_ROLLBACK?
			TransactionImpl tx = txThreadMap.Remove(Thread.CurrentThread);
			if (tx != null)
			{
			// generate pro-active event suspend
				tx.markAsSuspended();
			}
			return tx;
		}

		public void setRollbackOnly() throws IllegalStateException, SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
			Thread thread = Thread.CurrentThread;
			TransactionImpl tx = txThreadMap.get(thread);
			if (tx == null)
			{
				throw new IllegalStateException("Not in transaction");
			}
			tx.setRollbackOnly();
		}

		public void setTransactionTimeout(int seconds) throws SystemException
		{
			if (!tmOk)
			{
				throw new SystemException("TM has encountered some problem, " + "please perform neccesary action (tx recovery/restart)");
			}
		// ...
		}

		sbyte[] getBranchId(XAResource xaRes)
		{
			if (xaRes is XaResource)
			{
				sbyte[] branchId = ((XaResource) xaRes).getBranchId();
				if (branchId != null)
				{
					return branchId;
				}
			}
			return xaDsManager.getBranchId(xaRes);
		}

		XAResource getXaResource(sbyte branchId[])
		{
			return xaDsManager.getXaResource(branchId);
		}

		string getTxStatusAsString(int status)
		{
			switch (status)
			{
				case Status.STATUS_ACTIVE:
					return "STATUS_ACTIVE";
				case Status.STATUS_NO_TRANSACTION:
					return "STATUS_NO_TRANSACTION";
				case Status.STATUS_PREPARING:
					return "STATUS_PREPARING";
				case Status.STATUS_PREPARED:
					return "STATUS_PREPARED";
				case Status.STATUS_COMMITTING:
					return "STATUS_COMMITING";
				case Status.STATUS_COMMITTED:
					return "STATUS_COMMITED";
				case Status.STATUS_ROLLING_BACK:
					return "STATUS_ROLLING_BACK";
				case Status.STATUS_ROLLEDBACK:
					return "STATUS_ROLLEDBACK";
				case Status.STATUS_UNKNOWN:
					return "STATUS_UNKNOWN";
				case Status.STATUS_MARKED_ROLLBACK:
					return "STATUS_MARKED_ROLLBACK";
				default:
					return "STATUS_UNKNOWN(" + status + ")";
			}
		}

		public synchronized void dumpTransactions()
		{
			IEnumerator <TransactionImpl> itr = txThreadMap.values().GetEnumerator();
			if (!itr.MoveNext())
			{
				Console.WriteLine("No uncompleted transactions");
				return;
			}
			Console.WriteLine("Uncompleted transactions found: ");
			while (itr.MoveNext())
			{
				Console.WriteLine(itr.Current);
			}
		}

		public int getEventIdentifier()
		{
			TransactionImpl tx = (TransactionImpl) getTransaction();
			if (tx != null)
			{
				return tx.getEventIdentifier();
			}
			return -1;
		}
	}

}